#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/epoll.h>
#include "easylog.hpp"

const int   default_timeout = -1;
const int   default_fd      = -1;
const int   default_size    = 20;

enum event_type
{
    epnil = 0,
    epin  = EPOLLIN,
    epet  = EPOLLET,
    epout = EPOLLOUT,
    epio  = EPOLLIN  | EPOLLOUT,
    eperr = EPOLLERR | EPOLLHUP,
};

namespace server
{

struct fdevent
{
    fdevent(int fd, int event) : _fd(fd), _event(event) {}
    int _fd;
    int _event;
};

struct fdev_in  : public fdevent { fdev_in (int fd, bool etmode = false) : fdevent(fd, etmode ? epet|epin  : epin ) {} };
struct fdev_out : public fdevent { fdev_out(int fd, bool etmode = false) : fdevent(fd, etmode ? epet|epout : epout) {} };
struct fdev_io  : public fdevent { fdev_io (int fd, bool etmode = false) : fdevent(fd, etmode ? epet|epio  : epio ) {} };
struct fdev_err : public fdevent { fdev_err(int fd, bool etmode = false) : fdevent(fd, etmode ? epet|eperr : eperr) {} };

class epoll
{
public:
    epoll(int size = default_size, int timeout = default_timeout) 
        : _epfd(default_fd), _timeout(timeout), _size(size), _epevs(new struct epoll_event[size])
    {}

    ~epoll() { close(_epfd); }

    void create(int size = 128)
    {
        _epfd = epoll_create(size);

        if (_epfd < 0) 
            throw std::runtime_error("epoll create failed");
    }

    void add_event(const fdevent& fe) { control(EPOLL_CTL_ADD, fe); }
    void del_event(const fdevent& fe) { control(EPOLL_CTL_DEL, fe); }
    void mod_event(const fdevent& fe) { control(EPOLL_CTL_MOD, fe); }

    int wait(int timeout)
    {
        return epoll_wait(_epfd, _epevs.get(), _size, timeout);
    }

    std::unique_ptr<struct epoll_event[]>& events() { return _epevs; }

    struct epoll_event& operator[](int i)
    {
        assert(i < _size);
        return _epevs[i];
    }

private:
    void control(int op, const fdevent& fe)
    {
        struct epoll_event ev;
        ev.events = fe._event;
        ev.data.fd = fe._fd; // 设置fd方便事件就绪后取用，底层不会作修改

        if (epoll_ctl(_epfd, op, fe._fd, &ev) < 0)
            WARN("epoll add event failed, %d, %s", errno, strerror(errno));
    }

private:
    int _epfd;
    int _timeout;
    int _size;
    std::unique_ptr<struct epoll_event[]> _epevs;
};

}
